Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Image] Add onLoadStart, onLoadProgress, onLoadError events to Image component #1318

Closed
wants to merge 1 commit into from

Conversation

ptmt
Copy link
Contributor

@ptmt ptmt commented May 17, 2015

This PR adds 4 native events to NetworkImage.

demo

Using these events I could wrap Image component into something like:

class NetworkImage extends React.Component {
  getInitialState() {
    return {
      downloading: false,
      progress: 0
    }
  }

  render() {
    var loader = this.state.downloading ?
      <View style={this.props.loaderStyles}>
        <ActivityIndicatorIOS animating={true} size={'large'} />
        <Text style={{color: '#bbb'}}>{this.state.progress}%</Text>
      </View>
      :
      null;

    return <Image source={this.props.source}
      onLoadStart={() => this.setState({downloading: true}) }
      onLoaded={() => this.setState({downloading: false}) }
      onLoadProgress={(e)=> this.setState({progress: Math.round(100 * e.nativeEvent.written / e.nativeEvent.total)});
      onLoadError={(e)=> {
        alert('the image cannot be downloaded because: ', JSON.stringify(e));
        this.setState({downloading: false});
      }}>
      {loader}
    </Image>
  }
}

Useful on slow connections and server errors.

There are dozen lines of Objective C, which I don't have experience with. There are neither specific tests nor documentation yet. And I do realize that you're already working right now on better <Image/> (pipeline, new asset management, etc.). So this is basically a proof concept of events for images, and if this idea is not completely wrong I could improve it or help somehow.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 17, 2015

@interface RCTDownloadTaskWrapper : NSObject <NSURLSessionDownloadDelegate>

@property(nonatomic,assign) id<NSURLSessionDownloadDelegate> delegate;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: One space after @​property.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delegates should have "weak" retain semantics.

@a2
Copy link
Contributor

a2 commented May 18, 2015

Sorry for all the nitpicks.

@a2 a2 self-assigned this May 18, 2015
@ptmt
Copy link
Contributor Author

ptmt commented May 19, 2015

Thank you so much! Could you help me about tests and other possible performances issues? As you mentioned before NSURLSession which creating each time. For now I use it in the app with a lot of images loading - everything is fine, but I understand these changes could affect somebody. Or should I move it in external library?

{
NSString *cacheKey = RCTCacheKeyForURL(url);
__weak RCTImageDownloader *weakSelf = self;
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
return [self _downloadDataForURL:url progressBlock:(RCTDataProgressBlock)progressBlock block:^(BOOL cached, NSData *data, NSError *error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to cast the blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@brentvatne
Copy link
Collaborator

Thanks for all of the work here people, looking forward to seeing this in 😄

@ptmt
Copy link
Contributor Author

ptmt commented May 29, 2015

Merged from upstream (new lines about resizing etc), I've got unnecessary commits, it looks unclean now. I use this branch in production, though, don't know what to do next exactly. Tests? Rebase?

@brentvatne
Copy link
Collaborator

@a2 - any chance you have a bit of time to ❤️ this PR?

@ide
Copy link
Contributor

ide commented May 29, 2015

As for the API you may want to study https://html.spec.whatwg.org/multipage/embedded-content.html#mediaevents to see if any of the concepts are worth sharing (cc @vjeux)

@ptmt
Copy link
Contributor Author

ptmt commented May 30, 2015

@ide thanks, good point. I consider to adding the abort event. And about events' naming conventions, not sure how to do it right. I've used https://facebook.github.io/react-native/docs/textinput.html#onchange pattern on{eventName}. It's also similar to the current state of https://github.com/brentvatne/react-native-video, but it makes sense to rename methods. So we will have:
loadstart, progress, abort, error, loadeddata.

@brentvatne brentvatne changed the title Add onDownloadProgress, onError events to Image component [Image] Add onDownloadProgress, onError events to Image component May 30, 2015
@brentvatne
Copy link
Collaborator

@vjeux - can you shed any light on how you'd like to do naming for these types of events, as per @UnknownException comment above?

@browniefed
Copy link
Contributor

This would be super useful, especially if the loaded data got passed back on success in base64 encoded string or some workable format.

@a2
Copy link
Contributor

a2 commented Jun 1, 2015

Waiting on event naming convention comments.

@vjeux
Copy link
Contributor

vjeux commented Jun 1, 2015

I like the naming from the spec:

  • onLoadStart
  • onLoadProgress
  • onLoadAbort
  • onLoadError
  • onLoaded

This would be super useful, especially if the loaded data got passed back on success in base64 encoded string or some workable format.

No. We never want to read image data in js. This means sending a huge chunk of memory (several megabytes) to js. There are some places where js is not a good language and doing image processing is one of them. You should be manipulating the image using C or the GPU. Not js with a base64 encoded file.

_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() resizeMode:self.contentMode backgroundColor:self.backgroundColor block:^(UIImage *image, NSError *error) {
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale()
resizeMode:self.contentMode backgroundColor:self.backgroundColor
progressBlock:progressHandler block:^(UIImage *image, NSError *error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you not update progress if there is no onProgress callback setup in js? This is wasteful to send events that are not going to be handled. (I'm not familiar with iOS so i'm not sure if you already do that, sorry if it's the case)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. There is a slightly tricky edge case to handle: on the Image's first render pass we don't provide an onProgress handler, and then provide one on the second pass. So the view/viewmanager needs to enable/disable the progress events when the onProgress handler prop is set/removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for comments, I renamed events and tried to not fire the progress event if it's not defined.

Tested this behavior with something like this:

class TestNetworkImage extends Component {
  componentDidMount() {
      setTimeout(() => {
        this.setState({
          onProgress: this.onProgress.bind(this)
        })
      }, 1000);
    }

  render() {
    return <Image ref="image"
      source={{uri: this.props.source}}
      onLoadProgress={this.state.onProgress}>
      {this.props.loader}
      {this.props.children}
    </Image>
  }
}

NSLog shows that at first second ImageView doesn't send an event to js in that case, then it does.

@ptmt
Copy link
Contributor Author

ptmt commented Jul 7, 2015

The app which uses these changes has passed Apple review and available on AppStore. It's not a good thing to maintain the fork, so I need to figure out how to split it into the stand-alone plugin.

@brentvatne
Copy link
Collaborator

@UnknownException - I would love to see this merged upstream, @a2 - do you have any time to review again?

Also, @UnknownException - can you please rebase?

@a2
Copy link
Contributor

a2 commented Jul 7, 2015

@facebook-github-bot import

@facebook-github-bot
Copy link
Contributor

Thanks for importing. If you are an FB employee go to https://our.intern.facebook.com/intern/opensource/github/pull_request/470962509718878/int_phab to review.

@brentvatne
Copy link
Collaborator

❤️ @a2

@ptmt ptmt changed the title [Image] Add onDownloadProgress, onError events to Image component [Image] Add onLoadStart, onLoadProgress, onLoadError events to Image component Jul 8, 2015
@ptmt
Copy link
Contributor Author

ptmt commented Jul 8, 2015

Synced with the master and rebased. I had to adopt the new caching mechanism (NSURLCache) to make this whole thing work with NSURLSessionDownloadTask instead of NSURLSessionDataTask. Please, review, if you can.
Should I add examples to UIExplore?

@brentvatne
Copy link
Collaborator

@UnknownException - if you can add an example to UIExplorer and submit in a separate PR that would be lovely!

ayushjainrksh pushed a commit to MLH-Fellowship/react-native that referenced this pull request Jul 2, 2020
There are some code snippets that use `var` for declaring `styles` whereas some other snippets use `const`.
Modified the page to all use `const` for consistency.
ryanlntn pushed a commit to ryanlntn/react-native that referenced this pull request Aug 9, 2022
…cebook#439)" (facebook#1318)

This change reverts microsoft#459 - but still tries to address the original issues:
- microsoft#422
- microsoft#322

This also addresses an issue when programmatically resizing windows where the RCTRootContentView may end up at the wrong size because an in-flight layout gets resolved after the resize on the main thread.
We now keep sync dispatch on the shadow queue for live resizing windows (to prevent tearing) but also dispatch async (as done on iOS) so the latest new size is sure to win.
The block has a check to bail if the size doesn't change, so this isn't a perf drain running the block twice.

Co-authored-by: Scott Kyle <skyle@fb.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants